package fr.upmc.i3s.fuglmeyeranalyser.models;

import fr.upmc.i3s.fuglmeyeranalyser.models.solutions.AbstractMCOSolution;
import fr.upmc.i3s.fuglmeyeranalyser.models.solutions.MCOSolutionLinear;
import fr.upmc.i3s.fuglmeyeranalyser.models.solutions.AbstractMCPSolution;
import fr.upmc.i3s.fuglmeyeranalyser.models.solutions.MCPSolutionLinear;
import fr.upmc.i3s.fuglmeyeranalyser.models.solutions.AbstractMCPkSolution;
import fr.upmc.i3s.fuglmeyeranalyser.models.solutions.MCPkSolutionLinear;
import fr.upmc.i3s.fuglmeyeranalyser.patients.FMScore;
import fr.upmc.i3s.fuglmeyeranalyser.patients.Patient;

public class SaveOfLinearModel extends Model {
	
	  /////////////////////////////////
	 // SOLVING METHODS FOR PATIENT //
	/////////////////////////////////
	
	@Override
	public AbstractMCPkSolution solveMCPkSystemForPatient(Patient patient) {
		double inverseSigma2Sum = 0;
		double delayOnSigma2Sum = 0;
		double scoreOnSigma2Sum = 0;
		double delay2OnSigma2Sum = 0;
		double delayXscoreOnSigma2Sum = 0;
		double averageScore = 0;
		int nbScore = patient.getScoreList().size();
		// compute average score (for sigma computations)
		for(FMScore score : patient.getScoreList()) {
			averageScore += (score.getFuglMeyerScore() / nbScore);
		}
		// compute useful sums
		for(FMScore score : patient.getScoreList()) {
			double sigma = (score.getFuglMeyerScore() - averageScore);
			// in case of sigma != 0, take account the weight for computations
			if(sigma!=0) {
				inverseSigma2Sum += (1 / Math.pow(sigma, 2));
				delayOnSigma2Sum += (score.getDelayAfterStroke() / Math.pow(sigma, 2));
				scoreOnSigma2Sum += (score.getFuglMeyerScore() / Math.pow(sigma, 2));
				delay2OnSigma2Sum += (Math.pow(score.getDelayAfterStroke(), 2) / Math.pow(sigma, 2));
				delayXscoreOnSigma2Sum += ((score.getDelayAfterStroke()*score.getFuglMeyerScore()) / Math.pow(sigma, 2));
			}
			// in case of sigma = 0, consider this score as uninfluential for computations
		}
		// compute solution (beta & FM0 & valuation of kapa) 
		MCPkSolutionLinear solution = new MCPkSolutionLinear(patient);
		do {
			double kapa = 1; //TODO select a value for kapa
			double fm0 = computeFM0forMCPk(inverseSigma2Sum, delayOnSigma2Sum, scoreOnSigma2Sum, delay2OnSigma2Sum, delayXscoreOnSigma2Sum, kapa);
			double beta = computeBetaforMCPk(inverseSigma2Sum, delayOnSigma2Sum, scoreOnSigma2Sum, delay2OnSigma2Sum, delayXscoreOnSigma2Sum, kapa);
			solution.setKapa(kapa);
			solution.setFm0(fm0);
			solution.setBeta(beta);
			double valuationGap1 = checkKapaValuationFromEquation1(kapa, fm0, beta, inverseSigma2Sum, delayOnSigma2Sum, scoreOnSigma2Sum);
			double valuationGap2 = checkKapaValuationFromEquation2(kapa, fm0, beta, delay2OnSigma2Sum, delayOnSigma2Sum, delayXscoreOnSigma2Sum);
			double valuationGap3 = checkKapaValuationFromEquation3(kapa, fm0, beta, inverseSigma2Sum, delay2OnSigma2Sum, delayOnSigma2Sum, scoreOnSigma2Sum, delayXscoreOnSigma2Sum);
			System.out.println("kapa valuation gap 1 = "+valuationGap1);
			System.out.println("kapa valuation gap 2 = "+valuationGap2);
			System.out.println("kapa valuation gap 3 = "+valuationGap3);
		} while (1 == 2);
		return solution;
	}
	
	
	@Override
	public AbstractMCPSolution solveMCPSystemForPatient(Patient patient) {
		double inverseSigma2Sum = 0;
		double delayOnSigma2Sum = 0;
		double scoreOnSigma2Sum = 0;
		double delay2OnSigma2Sum = 0;
		double delayXscoreOnSigma2Sum = 0;
		double averageScore = 0;
		int nbScore = patient.getScoreList().size();
		// compute average score (for sigma computations)
		for(FMScore score : patient.getScoreList()) {
			averageScore += (score.getFuglMeyerScore() / nbScore);
		}
		// compute useful sums
		for(FMScore score : patient.getScoreList()) {
			double sigma = (score.getFuglMeyerScore() - averageScore);
			// in case of sigma != 0, take account the weight for computations
			if(sigma!=0) {
				inverseSigma2Sum += (1 / Math.pow(sigma, 2));
				delayOnSigma2Sum += (score.getDelayAfterStroke() / Math.pow(sigma, 2));
				scoreOnSigma2Sum += (score.getFuglMeyerScore() / Math.pow(sigma, 2));
				delay2OnSigma2Sum += (Math.pow(score.getDelayAfterStroke(), 2) / Math.pow(sigma, 2));
				delayXscoreOnSigma2Sum += ((score.getDelayAfterStroke()*score.getFuglMeyerScore()) / Math.pow(sigma, 2));
			}
			// in case of sigma = 0, consider this score as uninfluential for computations
		}
		// compute solution (beta & FM0)
		MCPSolutionLinear solution = new MCPSolutionLinear(patient);
		double fm0 = computeFM0forMCP(inverseSigma2Sum, delayOnSigma2Sum, scoreOnSigma2Sum, delay2OnSigma2Sum, delayXscoreOnSigma2Sum);
		double beta = computeBetaforMCP(inverseSigma2Sum, delayOnSigma2Sum, scoreOnSigma2Sum, delay2OnSigma2Sum, delayXscoreOnSigma2Sum);
		solution.setFm0(fm0);
		solution.setBeta(beta);
		return solution;
	}
	

	@Override
	public AbstractMCOSolution solveMCOSystemForPatient(Patient patient) {
		double scoreSum = 0;
		double delaySum = 0;
		double scoreXdelaySum = 0;
		double squaredDelaySum = 0;
		int nbScore = patient.getScoreList().size();
		// compute useful sums
		for(FMScore score : patient.getScoreList()) {
			scoreSum += score.getFuglMeyerScore();
			delaySum += score.getDelayAfterStroke();
			scoreXdelaySum += (score.getFuglMeyerScore()*score.getDelayAfterStroke());
			squaredDelaySum += Math.pow(score.getDelayAfterStroke(), 2);
		}
		// compute solution (beta & FM0)
		MCOSolutionLinear solution = new MCOSolutionLinear(patient);
		double fm0 = computeFM0ForMCO(delaySum, scoreXdelaySum, squaredDelaySum, scoreSum, nbScore);
		double beta = computeBetaForMCO(delaySum, scoreXdelaySum, squaredDelaySum, scoreSum, nbScore);
		solution.setFm0(fm0);
		solution.setBeta(beta);
		return solution;
	}
	
	
	  ///////////////////////////////////////////////////////////////////
	 // INNER COMPUTERS ////////////////////////////////////////////////
	///////////////////////////////////////////////////////////////////
	
	// MCP-k ///////////////////////////////////////////////////////////
	
	/**
	 * Compute FM0 for MCP-k
	 * @param inverseSigma2Sum
	 * @param delayOnSigma2Sum
	 * @param scoreOnSigma2Sum
	 * @param delay2OnSigma2Sum
	 * @param delayXscoreOnSigma2Sum
	 * @param kapa
	 * @return
	 */
	private double computeFM0forMCPk(double inverseSigma2Sum, double delayOnSigma2Sum, 
			double scoreOnSigma2Sum, double delay2OnSigma2Sum, double delayXscoreOnSigma2Sum, double kapa) {
		return kapa * ( ( (delay2OnSigma2Sum*scoreOnSigma2Sum) - (delayOnSigma2Sum*delayXscoreOnSigma2Sum) ) /
					( (delay2OnSigma2Sum*inverseSigma2Sum) - Math.pow(delayOnSigma2Sum, 2) ) ); 
	}
	
	/**
	 * Compute Beta for MCP-k
	 * @param inverseSigma2Sum
	 * @param delayOnSigma2Sum
	 * @param scoreOnSigma2Sum
	 * @param delay2OnSigma2Sum
	 * @param delayXscoreOnSigma2Sum
	 * @param kapa
	 * @return
	 */
	private double computeBetaforMCPk(double inverseSigma2Sum, double delayOnSigma2Sum, 
			double scoreOnSigma2Sum, double delay2OnSigma2Sum, double delayXscoreOnSigma2Sum, double kapa) {
		return kapa * ( ( (delayXscoreOnSigma2Sum*inverseSigma2Sum) - (delayOnSigma2Sum*scoreOnSigma2Sum) ) /
					(  (delay2OnSigma2Sum*inverseSigma2Sum) - Math.pow(delayOnSigma2Sum, 2) ) );
	}
	
	/**
	 * Check kapa valuation with computed FM0 & beta values from equation 1
	 * @param kapa
	 * @param fm0
	 * @param beta
	 * @param inverseSigma2Sum
	 * @param delayOnSigma2Sum
	 * @param scoreOnSigma2Sum
	 * @return the gap between the computed solution and 0 (a value of 0 indicates kapa is the best valuation possible)
	 */
	private double checkKapaValuationFromEquation1(double kapa, double fm0, double beta,
			double inverseSigma2Sum, double delayOnSigma2Sum, double scoreOnSigma2Sum) {
		return (fm0*inverseSigma2Sum) 
				+ (beta*delayOnSigma2Sum)
				- (kapa*scoreOnSigma2Sum);
	}
	
	/**
	 * Check kapa valuation with computed FM0 & beta values from equation 2
	 * @param kapa
	 * @param fm0
	 * @param beta
	 * @param delay2OnSigma2Sum
	 * @param delayOnSigma2Sum
	 * @param delayXscoreOnSigma2Sum
	 * @return the gap between the computed solution and 0 (a value of 0 indicates kapa is the best valuation possible)
	 */
	private double checkKapaValuationFromEquation2(double kapa, double fm0, double beta,
			double delay2OnSigma2Sum, double delayOnSigma2Sum, double delayXscoreOnSigma2Sum) {
		return (fm0*delayOnSigma2Sum) 
				+ (beta*delay2OnSigma2Sum)
				- (kapa*delayXscoreOnSigma2Sum);
	}
	
	/**
	 * Check kapa valuation with computed FM0 & beta values from equation 3
	 * @param kapa
	 * @param fm0
	 * @param beta
	 * @param inverseSigma2Sum
	 * @param delay2OnSigma2Sum
	 * @param delayOnSigma2Sum
	 * @param scoreOnSigma2Sum
	 * @param delayXscoreOnSigma2Sum
	 * @return the gap between the computed solution and 0 (a value of 0 indicates kapa is the best valuation possible)
	 */
	private double checkKapaValuationFromEquation3(double kapa, double fm0, double beta, double inverseSigma2Sum,
			double delay2OnSigma2Sum, double delayOnSigma2Sum, double scoreOnSigma2Sum, double delayXscoreOnSigma2Sum) {
		return (Math.pow(fm0, 2)*inverseSigma2Sum)
				+ (Math.pow(beta, 2)*delay2OnSigma2Sum) 
				+ (2*fm0*beta*delayOnSigma2Sum)
				- (kapa*fm0*scoreOnSigma2Sum)
				- (kapa*beta*delayXscoreOnSigma2Sum);
	}
	
	
	// MCP /////////////////////////////////////////////////////////////
	
	/**
	 * Compute FM0 for MCP
	 * @param inverseSigma2Sum
	 * @param delayOnSigma2Sum
	 * @param scoreOnSigma2Sum
	 * @param delay2OnSigma2Sum
	 * @param delayXscoreOnSigma2Sum
	 * @return
	 */
	private double computeFM0forMCP(double inverseSigma2Sum, double delayOnSigma2Sum, 
			double scoreOnSigma2Sum, double delay2OnSigma2Sum, double delayXscoreOnSigma2Sum) {
		return ( (delay2OnSigma2Sum*scoreOnSigma2Sum) - (delayOnSigma2Sum*delayXscoreOnSigma2Sum) ) /
					( (delay2OnSigma2Sum*inverseSigma2Sum) - Math.pow(delayOnSigma2Sum, 2) ); 
	}
	
	/**
	 * Compute Beta for MCP
	 * @param inverseSigma2Sum
	 * @param delayOnSigma2Sum
	 * @param scoreOnSigma2Sum
	 * @param delay2OnSigma2Sum
	 * @param delayXscoreOnSigma2Sum
	 * @return
	 */
	private double computeBetaforMCP(double inverseSigma2Sum, double delayOnSigma2Sum, 
			double scoreOnSigma2Sum, double delay2OnSigma2Sum, double delayXscoreOnSigma2Sum) {
		return ( (delayXscoreOnSigma2Sum*inverseSigma2Sum) - (delayOnSigma2Sum*scoreOnSigma2Sum) ) /
					(  (delay2OnSigma2Sum*inverseSigma2Sum) - Math.pow(delayOnSigma2Sum, 2) );
	}
	
	// MCO /////////////////////////////////////////////////////////////
	
	/**
	 * Compute FM0 for MCO
	 * @param delaySum
	 * @param scoreXdelaySum
	 * @param squaredDelaySum
	 * @param scoreSum
	 * @param nbScore
	 * @return
	 */
	private double computeFM0ForMCO(double delaySum, double scoreXdelaySum, 
			double squaredDelaySum, double scoreSum, int nbScore) {
		return ( delaySum*scoreXdelaySum - squaredDelaySum*scoreSum ) / 
															( Math.pow(delaySum,2) - nbScore*squaredDelaySum ) ;		
	}
	
	/**
	 * Compute Beta for MCO
	 * @param delaySum
	 * @param scoreXdelaySum
	 * @param squaredDelaySum
	 * @param scoreSum
	 * @param nbScore
	 * @return
	 */
	private double computeBetaForMCO(double delaySum, double scoreXdelaySum, 
			double squaredDelaySum, double scoreSum, int nbScore) {
		return ( delaySum*scoreSum - nbScore*scoreXdelaySum ) / 
															( Math.pow(delaySum,2) - nbScore*squaredDelaySum ) ;	
	}

}
